package com.gujiayue.common.config;

import java.io.IOException;
import java.io.PrintWriter;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import com.gujiayue.common.encrypt.MyPasswordEncoder;
import com.gujiayue.common.interceptor.CaptchaCodeFilter;
import com.gujiayue.common.interceptor.DecisionManager;
import com.gujiayue.common.interceptor.SecurityFilter;
import com.gujiayue.common.interceptor.SecurityMetadataSource;
import com.gujiayue.common.restful.RestfulRtnInfo;
import com.gujiayue.common.utils.JsonUtils;
import com.gujiayue.module.sys.pojo.SysUsrDo;
import com.gujiayue.module.sys.service.SecurityService;

/**
 
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	protected static Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

	@Resource
	private MyPasswordEncoder myPasswordEncoder;

	@Resource
	private SecurityService securityService;

	@Resource
	private DecisionManager decisionManager;

	@Resource
	private SecurityMetadataSource securityMetadataSource;

	@Resource
	private SecurityFilter securityFilter;

	@Resource
	private CaptchaCodeFilter captchaCodeFilter;

	@Resource
	private DataSource dataSource;

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.authenticationProvider(authenticationProvider()).httpBasic().
		// 未登录时
		authenticationEntryPoint((request, response, authException) -> {
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(40012, "未登录")));
			out.flush();
			out.close();
		}).
		// 必须授权才能范围AbstractSecurityInterceptor
		// requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
		// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
		and().authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
			@Override
			public <O extends FilterSecurityInterceptor> O postProcess(O o) {
				o.setAccessDecisionManager(decisionManager);// 决策管理器
				o.setSecurityMetadataSource(securityMetadataSource);// 安全元数据源
				return o;
			}
		}).
		// 使用自带的登录
		and().formLogin().
		// 登录失败，返回json
		permitAll().failureHandler((request, response, ex) -> {
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			StringBuffer reason = new StringBuffer();
			if (ex instanceof AccountExpiredException) {
				reason.append("账号过期!");
			} else if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
				reason.append("用户名或密码错误");
			} else if (ex instanceof CredentialsExpiredException) {
				reason.append("密码过期");
			} else if (ex instanceof DisabledException) {
				reason.append("账户被禁用");
			} else if (ex instanceof LockedException) {
				reason.append("账号锁定!");
			} else if (ex instanceof InternalAuthenticationServiceException) {
				reason.append("用户不存在!");
			} else if (ex instanceof AuthenticationServiceException) {
				reason.append("验证码不能为空!");
			} else if (ex instanceof AuthenticationServiceException) {
				reason.append("验证码错误!");
			} else {
				reason.append("登录失败!");
			}
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(400, reason.toString())));
			out.flush();
			out.close();
		}).
		// 登录成功，返回json
		successHandler((request, response, authentication) -> {
			SysUsrDo usrDo = (SysUsrDo) authentication.getPrincipal();
			System.out.println(JsonUtils.obeyJson(usrDo));
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(40010, "登录成功", authentication)));
			out.flush();
			out.close();
		}).
		// 没有权限，返回json
		and().exceptionHandling().accessDeniedHandler((request, response, ex) -> {
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(403, "权限不足")));
			out.flush();
			out.close();
		}).// 未登录时
		authenticationEntryPoint(new AuthenticationEntryPoint() {
			@Override
			public void commence(HttpServletRequest req, HttpServletResponse resp,
					AuthenticationException authException) throws IOException, ServletException {
				resp.setContentType("application/json;charset=utf-8");
				PrintWriter out = resp.getWriter();
				String reason = "未登录";
				if (authException instanceof InsufficientAuthenticationException) {
					reason = "您当前为未登录!";
				}
				out.write(JsonUtils.obeyJson(new RestfulRtnInfo(40012, reason)));
				out.flush();
				out.close();
			}
		}).and().
		// 记住我的配置
		rememberMe().tokenRepository(persistentTokenRepository()).tokenValiditySeconds(3600). // 设置记住我的过期时间
		userDetailsService(securityService).
		// 退出成功，返回json
		and().logout().logoutSuccessHandler((request, response, authentication) -> {
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(200, "退出成功", authentication)));
			out.flush();
			out.close();
		}).deleteCookies("JESSIONID").permitAll().
		// 会话管理.同一账号同时登录最大用户数.会话失效(账号被挤下线)处理逻辑
		and().sessionManagement().maximumSessions(1).expiredSessionStrategy((event) -> {
			HttpServletResponse response = event.getResponse();
			response.setContentType("application/json;charset=utf-8");
			PrintWriter out = response.getWriter();
			out.write(JsonUtils.obeyJson(new RestfulRtnInfo(200, "退出成功")));
			out.flush();
			out.close();
		});
		// 验证码拦截
		http.addFilterBefore(captchaCodeFilter, UsernamePasswordAuthenticationFilter.class); // 添加验证码校验过滤器
		// 路径拦截
		http.addFilterBefore(securityFilter, FilterSecurityInterceptor.class);
		// 开启跨域访问
		http.cors();// .disable();
		// 开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
		http.csrf().disable();
	}

	@Override
	public void configure(WebSecurity web) {
		// 对于在header里面增加token等类似情况，放行所有OPTIONS请求。
		web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
	}

	@Bean
	public AuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		// 对默认的UserDetailsService进行覆盖
		authenticationProvider.setUserDetailsService(securityService);
		authenticationProvider.setPasswordEncoder(myPasswordEncoder);
		return authenticationProvider;
	}

	/**
	 * 记住我功能的token存取器配置
	 * 
	 * @return
	 */
	@Bean
	public PersistentTokenRepository persistentTokenRepository() {
		JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
		tokenRepository.setDataSource(dataSource);
		// 首次加载创建表
		// tokenRepository.setCreateTableOnStartup(true);
		return tokenRepository;
	}
}
